#include "OP_CreateCylinder.h"
#include "../Geometries/PolygonMesh.h"

using namespace std;
using namespace CICore;
using namespace CIGeom;
using namespace CIView;

///<************************************************************************************************************>
///<************************************* region CONSTANTS DEFINITION ******************************************>
///<************************************************************************************************************>

/***************************************************************************************************************
************************************** endregion CONSTANTS DEFINITION ******************************************
****************************************************************************************************************/












/// <summary> 
/// Constructor 
/// </summary>
/// <param name="v"> A reference to the vector that contains the four 3D views pointers </param>
CIOps::OP_CreateCylinder::OP_CreateCylinder (vector<CIView::View*>& v)
: Operation (v), cylinder (0), radius (0.002f), height (0.004f), cylinderDetail (1), rotation_x (0) {

    newSelectionMode = scene.GetSelectionMode();

    stacks = OperationManager::Stacks;
    slices = OperationManager::Slices;
}

/// <summary> 
/// Destructor 
/// </summary>
CIOps::OP_CreateCylinder::~OP_CreateCylinder() {
    if (status != COMPLETED)
        delete cylinder;
}










///<************************************************************************************************************>
///<**************************************** region Utility Methods ********************************************>
///<************************************************************************************************************>

/// <summary> 
/// Method called to execute directly the operation 
/// </summary>
/// <returns> The operation status </returns>
int CIOps::OP_CreateCylinder::Exec() {
    switch (status) {
        case INITIAL:
            status = WAITING_FOR_EVENTS;
            break;
    }
    return status;        
}

/// <summary> 
/// Method called to undo the operation 
/// </summary>
void CIOps::OP_CreateCylinder::Undo() {
    status = UNDO;      

    scene.RemoveGeometry (cylinder->GetName().c_str());   //Removes the geometry from the 3D scene
    RestoreOldSelection();                              //Restores the old selection status
    UpdateViews();                                      //Updates the 3D views
}

/// <summary> 
/// Method called to redo the operation 
/// </summary>
void CIOps::OP_CreateCylinder::Redo() {
    status = COMPLETED;    

    scene.AddGeometry (cylinder);                     //Re-adds the geometry in the 3D scene  
    RestoreNewSelection();                          //Restore the new selectio status
    UpdateViews();                                  //Updates the 3D views
}

/// <summary> 
/// Mouse Pressed event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse press event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Pressed event </param>
void CIOps::OP_CreateCylinder::MousePressEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();
    
    SaveOldSelection();                             //Saves the old selection status
    scene.DeselectAllFaces();                       //Deselects all faces       //TODO -> Deselect all (included geometries and groups)

    view->WindowToWorldCoordinates (&x, &y);        //Unproject the mouse cursor coordinates

    Orientation& cameraOrient = view->GetCameraOrientation();

    //Computing the cylinder topLeft according to the current camera of the view that has thrown the event
    switch (view->GetCameraType()) {
        case Camera::TOP:
            topLeft.x = x;
            topLeft.z = y;
            rotation_x = 90.0f;
            break;
        
        case Camera::FRONT:
            topLeft.x = x;
            topLeft.y = -y;
            break;
        
        case Camera::RIGHT:            
            topLeft.y = -y;
            topLeft.z = -x;
            break;
    }

    unsigned int id = scene.GenerateNewGeometryID();      //Gets a reference to the number of geometries that have been added to the scene
    stringstream name;
    name << "Cylinder" << id;                               //Sets the name of the new triangle mesh 

    cylinder = new PolygonMesh (name.str());                     //Creates the cylinder 

    CreateCylinderWithTriangles (stacks*cylinderDetail, 
                                 slices*cylinderDetail, 
                                 height, 
                                 radius);           //Creates the cylinder with triangles as faces

    scene.AddGeometry (cylinder);                                 //Adds the cylinder in the 3D scene
    
    UpdateViews();                                              //Updates the 3D views
}

/// <summary> 
/// Mouse Released event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse release event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Released event </param>
void CIOps::OP_CreateCylinder::MouseReleaseEvent (CIView::View* view, QMouseEvent* e) {
    status = COMPLETED;
    
    view->SetPrimitivesSelected (true);                     //Signals that there is a primitive selected

    SaveNewSelection();                     //Saves the new selection status
    cylinder->UpdateBoundingBox();            //Updates the cylinder bounding box
}

/// <summary> 
/// Mouse Moved event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse move event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Moved event </param>
void CIOps::OP_CreateCylinder::MouseMoveEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();

    view->WindowToWorldCoordinates (&x, &y);            //Unproject the mouse cursor coordinates

    Orientation& cameraOrient = view->GetCameraOrientation();

    Vector3D drag;    
    float newRadius, scaleRadius; 
    float newHeight, scaleHeight;
    Matrix scaling;

    //Computing the new radius value according to the mouse drag values
    switch (view->GetCameraType()) {
        case Camera::TOP:
            drag.x = x - topLeft.x;
            drag.z = y - topLeft.z;
            newRadius = drag.x ? drag.x / 2.0f : radius;
            newHeight = drag.z ? drag.z : height;

            scaleRadius = newRadius / radius;
            scaleHeight = newHeight / height;

            scaling.SetScaling (scaleRadius, scaleRadius, scaleHeight);            
            break;
        
        case Camera::FRONT:
            drag.x = x - topLeft.x;
            drag.y = -topLeft.y - y;
            newRadius = drag.x ? drag.x / 2.0f : radius;
            newHeight = drag.y ? drag.y : height;

            scaleRadius = newRadius / radius;
            scaleHeight = newHeight / height;

            scaling.SetScaling (scaleRadius, scaleHeight, scaleRadius);         
            break;
        
        case Camera::RIGHT:         
            drag.z = -topLeft.z - x;
            drag.y = -topLeft.y - y;            
            newRadius = drag.z ? drag.z / 2.0f : radius;
            newHeight = drag.y ? drag.y : height;

            scaleRadius = newRadius / radius;
            scaleHeight = newHeight / height;

            scaling.SetScaling (scaleRadius, scaleHeight, scaleRadius);         
            break;
    }    

    height = newHeight;
    radius = newRadius;

    //Scaling the cylinder according to the new radius and height
    Matrix m1, m2;
    m1.SetTranslation (-topLeft.x, -topLeft.y, -topLeft.z);
    m2.SetTranslation (topLeft.x, topLeft.y, topLeft.z);
    
    Matrix m;
    m = m1*scaling*m2;
    cylinder->ApplyMatrix (m);

    UpdateViews();                                      //Updates the 3D views
}

/// <summary> 
/// Wheel event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse wheel event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Wheel event </param>
void CIOps::OP_CreateCylinder::WheelEvent (CIView::View* view, QWheelEvent* e) {
    if ((e->delta() > 0 && cylinderDetail == 4) || (e->delta() <= 1 && cylinderDetail == 1))
        return;

    e->delta() > 0 ? cylinderDetail++ : cylinderDetail--;   //Increases/Decreases the cylinder triangle tessellation detail

    if (stacks*cylinderDetail > 30 || slices*cylinderDetail > 30)
        return;

    cylinder->DeleteAllVertices();                        //Deletes the old cylinder vertices

    CreateCylinderWithTriangles (stacks*cylinderDetail, 
                                 slices*cylinderDetail, 
                                 height, 
                                 radius);    //Creates the cylinder with triangles as faces

    UpdateViews();                                      //Updates the 3D views
}
/***************************************************************************************************************
***************************************** endregion Utility Methods ********************************************
****************************************************************************************************************/









///<***********************************************************************************************************>
///<*********************************** region Triangles Tessellation *****************************************>
///<***********************************************************************************************************>

/// <summary> 
/// Method called to create a cylinder centered at the origin 
/// </summary>
/// <param name="stacks"> The number of stacks (for the cylinder subdivision in faces) </param>
/// <param name="slices"> The number of slices (for the cylinder subdivision in faces) </param>
/// <param name="height"> The cylinder height </param>
/// <param name="radius"> The cylinder radius </param>
void CIOps::OP_CreateCylinder::CreateCylinderWithTriangles (int stacks, int slices, float height, float radius) {
    double stackHeight = height / stacks;
    double sliceAngle = 2.0 * PI / slices;

    int i, j, v1_index, v2_index, v3_index;

    float y0 = 0.5 * height; 
    float y1 = y0 - stackHeight;

    double a = 0.0;                                 //The angle
    float x = radius * cos(0.0);                  
    float z = radius * sin(0.0);
                                              
       
    v1_index = cylinder->AddVertex (x, y0, z); 
    v2_index = cylinder->AddVertex (x, y1, z);           

    for (i=0; i<stacks; i++) {
        y0 = 0.5 * height - i * stackHeight;
        y1 = y0 - stackHeight;
        
        j = i ? 0 : 1;

        for (; j<=slices; j++) {
            a = j * sliceAngle;
            x = radius * cos(a);
            z = radius * sin(a);

            //Adding a face
            v3_index = cylinder->AddVertex (x, y0, z);  
            AddFace (v1_index, v2_index, v3_index);
            v1_index = v3_index;

            //Adding another one 
            v3_index = cylinder->AddVertex (x, y1, z);  
            AddFace (v1_index, v2_index, v3_index);
            v2_index = v3_index;
        }
    }

    double y = -height / 2.0f;

    /************Creating the top cap*************/    
    a = 0.0;
    x = radius * cos(a);
    z = radius * sin(a);
    v1_index = cylinder->AddVertex (x, y, z); 
    a += sliceAngle;

    // Center point
    v2_index = cylinder->AddVertex (0, y, 0); 

    for (j=1; j<=slices; j++) {
        x = radius * cos(a);
        z = radius * sin(a);

        //Adding a face
        v3_index = cylinder->AddVertex (x, y, z);  
        AddFace (v1_index, v2_index, v3_index);
        v1_index = v3_index;

        a += sliceAngle;
    }
    y += height;
    /*********************************************/


    /************Creating the bottom cap**********/
    // Center point
    v1_index = cylinder->AddVertex (0, y, 0); 

    a = 0.0;
    x = radius * cos(a);
    z = radius * sin(a);
    v2_index = cylinder->AddVertex (x, y, z); 
    a += sliceAngle;
    

    for (j=1; j<=slices; j++) {
        x = radius * cos(a);
        z = radius * sin(a);

        //Adding a face
        v3_index = cylinder->AddVertex (x, y, z);  
        AddFace (v1_index, v2_index, v3_index);
        v2_index = v3_index;

        a += sliceAngle;
    }
    y += height;
    /*********************************************/


    //Translating the cylinder vertices according to the position desidered
    Point cylinderCentre;                

    if (rotation_x == 0) {
         cylinderCentre.x = topLeft.x+radius;
         cylinderCentre.y = topLeft.y+height/2.0f;
         cylinderCentre.z = topLeft.z+radius;
    }
    else {
         cylinderCentre.x = topLeft.x+radius;
         cylinderCentre.y = topLeft.y+radius;
         cylinderCentre.z = topLeft.z+height/2.0f;
    }
    Matrix m1, m2, m;
    m1.SetRotation (rotation_x, 0, 0);
    m2.SetTranslation (cylinderCentre.x, cylinderCentre.y, cylinderCentre.z);
    m = m1 * m2;
    cylinder->ApplyMatrix (m);
}

/// <summary> 
/// Method called to add a face (triangle) to the cylinder
/// </summary>
/// <param name="v1"> The index of the first vertex int the cylinder vertex library </param>
/// <param name="v2"> The index of the second vertex int the cylinder vertex library </param>
/// <param name="v3"> The index of the third vertex int the cylinder vertex library </param>
void CIOps::OP_CreateCylinder::AddFace (int v1, int v2, int v3) {
    if (v1 == v2 || v1 == v3 || v2 == v3)
        return;

    VertexLib vertices(3);
    VertexLib& cylinderVertices = cylinder->GetVertices();
    Face* newFace;

    if (rotation_x == 0) {
        vertices[0] = cylinderVertices[v1];
        vertices[1] = cylinderVertices[v2];
        vertices[2] = cylinderVertices[v3];
    }
    else {
        vertices[0] = cylinderVertices[v2];
        vertices[1] = cylinderVertices[v1];
        vertices[2] = cylinderVertices[v3];
    }

    newFace = new Face (vertices);
    newFace->ComputeNormal();
    cylinder->AddFace (newFace);
}
/***************************************************************************************************************
*********************************** endregion Triangles Tessellation *******************************************
****************************************************************************************************************/
